﻿// dependency: tinymce_jquery, jstorage

$.fn.chapter_content_editor = function (api_upload_image, image_uploader) {

    image_upload_callback = function (field_name, url, type, win) {
        // TODO
    };

    container = this;

    var defs = {
        paste_auto_cleanup_on_paste: true,
        paste_enable_default_filters: true,
        paste_block_drop: false,
        paste_retain_style_properties: "none",
        paste_strip_class_attributes: "mso",
        paste_remove_spans: false,
        paste_remove_styles: false,
        paste_remove_styles_if_webkit: true,
        paste_convert_middot_lists: true,
        paste_convert_headers_to_strong: false,
        paste_dialog_width: "450",
        paste_dialog_height: "400",
        paste_text_use_dialog: false,
        paste_text_sticky: false,
        paste_text_sticky_default: false,
        paste_text_notifyalways: false,
        paste_text_linebreaktype: "combined",
        paste_text_replacements: [
                [/\u2026/g, "..."],
                [/[\x93\x94\u201c\u201d]/g, '"'],
                [/[\x60\x91\x92\u2018\u2019]/g, "'"]
            ]
    };

    function getParam(ed, name) {
        return ed.getParam(name, defs[name]);
    }

    $(container).tinymce({
        // Location of TinyMCE script
        script_url: '/Scripts/tiny_mce/tiny_mce.js',

        // General options
        theme: "advanced",
        plugins: "autolink,lists,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,advlist",

        // Theme options
        theme_advanced_buttons1: "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect",
        theme_advanced_buttons2: "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
        theme_advanced_buttons3: "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
        theme_advanced_buttons4: "insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak",
        theme_advanced_toolbar_location: "top",
        theme_advanced_toolbar_align: "left",
        theme_advanced_statusbar_location: "bottom",
        theme_advanced_resizing: true,

        file_browser_callback: image_upload_callback,

        content_css: "/Content/Site.css,/Content/Novel/Read.css",
        body_class: "chapter-content",
        apply_source_formatting: true,

        relative_urls: false,
        remove_script_host: true,

        paste_enable_default_filters: false,

        paste_preprocess: function (pl, o) {
            (function () {
                var each = tinymce.each;
                var ed = this.editor,
                h = o.content,
                grep = tinymce.grep,
                explode = tinymce.explode,
                trim = tinymce.trim,
                len, stripClass;

                //console.log('Before preprocess:' + o.content);

                function process(items) {
                    each(items, function (v) {
                        // Remove or replace
                        if (v.constructor == RegExp)
                            h = h.replace(v, '');
                        else
                            h = h.replace(v[0], v[1]);
                    });
                }

                // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser
                if (tinymce.isIE && document.documentMode >= 9) {
                    // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser
                    process([[/(?:<br>&nbsp;[\s\r\n]+|<br>)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br>&nbsp;[\s\r\n]+|<br>)*/g, '$1']]);

                    // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
                    process([
                    [/<br><br>/g, '<BR><BR>'], // Replace multiple BR elements with uppercase BR to keep them intact
                    [/<br>/g, ' '], // Replace single br elements with space since they are word wrap BR:s
                    [/<BR><BR>/g, '<br>'] // Replace back the double brs but into a single BR
                ]);
                }

                // Detect Word content and process it more aggressive
                if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) {
                    o.wordContent = true; 		// Mark the pasted contents as word specific content
                    //console.log('Word contents detected.');

                    // Process away some basic content
                    process([
                    /^\s*(&nbsp;)+/gi, 			// &nbsp; entities at the start of contents
                    /(&nbsp;|<br[^>]*>)+\s*$/gi		// &nbsp; entities at the end of contents
                ]);

                    if (getParam(ed, "paste_convert_headers_to_strong")) {
                        h = h.replace(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>");
                    }

                    if (getParam(ed, "paste_convert_middot_lists")) {
                        process([
                        [/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'], 				// Convert supportLists to a list item marker
                        [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], 	// Convert mso-list and symbol spans to item markers
                        [/(<p[^>]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__']				// Convert mso-list and symbol paragraphs to item markers (FF)
                    ]);
                    }

                    process([
                    // Word comments like conditional comments etc
                    /<!--[\s\S]+?-->/gi,

                    // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags
                    /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,

                    // Convert <s> into <strike> for line-though
                    [/<(\/?)s>/gi, "<$1strike>"],

                    // Replace nsbp entites to char since it's easier to handle
                    [/&nbsp;/gi, "\u00a0"]
                ]);

                    // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag.
                    // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot.
                    do {
                        len = h.length;
                        h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1");
                    } while (len != h.length);

                    // Remove all spans if no styles is to be retained
                    if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) {
                        h = h.replace(/<\/?span[^>]*>/gi, "");
                    } else {
                        // We're keeping styles, so at least clean them up.
                        // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx

                        process([
                        // Convert <span style="mso-spacerun:yes">___</span> to string of alternating breaking/non-breaking spaces of same length
                        [/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi,
                            function (str, spaces) {
                                return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length / 2)).split("").join("\u00a0") : "";
                            }
                        ],

                        // Examine all styles: delete junk, transform some, and keep the rest
                        [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,
                            function (str, tag, style) {
                                var n = [],
                                    i = 0,
                                    s = explode(trim(style).replace(/&quot;/gi, "'"), ";");

                                // Examine each style definition within the tag's style attribute
                                each(s, function (v) {
                                    var name, value,
                                        parts = explode(v, ":");

                                    function ensureUnits(v) {
                                        return v + ((v !== "0") && (/\d$/.test(v))) ? "px" : "";
                                    }

                                    if (parts.length == 2) {
                                        name = parts[0].toLowerCase();
                                        value = parts[1].toLowerCase();

                                        // Translate certain MS Office styles into their CSS equivalents
                                        switch (name) {
                                            case "mso-padding-alt":
                                            case "mso-padding-top-alt":
                                            case "mso-padding-right-alt":
                                            case "mso-padding-bottom-alt":
                                            case "mso-padding-left-alt":
                                            case "mso-margin-alt":
                                            case "mso-margin-top-alt":
                                            case "mso-margin-right-alt":
                                            case "mso-margin-bottom-alt":
                                            case "mso-margin-left-alt":
                                            case "mso-table-layout-alt":
                                            case "mso-height":
                                            case "mso-width":
                                            case "mso-vertical-align-alt":
                                                n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value);
                                                return;

                                            case "horiz-align":
                                                n[i++] = "text-align:" + value;
                                                return;

                                            case "vert-align":
                                                n[i++] = "vertical-align:" + value;
                                                return;

                                            case "font-color":
                                            case "mso-foreground":
                                                n[i++] = "color:" + value;
                                                return;

                                            case "mso-background":
                                            case "mso-highlight":
                                                n[i++] = "background:" + value;
                                                return;

                                            case "mso-default-height":
                                                n[i++] = "min-height:" + ensureUnits(value);
                                                return;

                                            case "mso-default-width":
                                                n[i++] = "min-width:" + ensureUnits(value);
                                                return;

                                            case "mso-padding-between-alt":
                                                n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value);
                                                return;

                                            case "text-line-through":
                                                if ((value == "single") || (value == "double")) {
                                                    n[i++] = "text-decoration:line-through";
                                                }
                                                return;

                                            case "mso-zero-height":
                                                if (value == "yes") {
                                                    n[i++] = "display:none";
                                                }
                                                return;
                                        }

                                        // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name
                                        if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) {
                                            return;
                                        }

                                        // If it reached this point, it must be a valid CSS style
                                        n[i++] = name + ":" + parts[1]; 	// Lower-case name, but keep value case
                                    }
                                });

                                // If style attribute contained any valid styles the re-write it; otherwise delete style attribute.
                                if (i > 0) {
                                    return tag + ' style="' + n.join(';') + '"';
                                } else {
                                    return tag;
                                }
                            }
                        ]
                    ]);
                    }
                }

                // Replace headers with <strong>
                if (getParam(ed, "paste_convert_headers_to_strong")) {
                    process([
                    [/<h[1-6][^>]*>/gi, "<p><strong>"],
                    [/<\/h[1-6][^>]*>/gi, "</strong></p>"]
                ]);
                }

                process([
                // Copy paste from Java like Open Office will produce this junk on FF
                [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, '']
            ]);

                // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso").
                // Note:-  paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation.
                stripClass = getParam(ed, "paste_strip_class_attributes");

                if (stripClass !== "none") {
                    function removeClasses(match, g1) {
                        if (stripClass === "all")
                            return '';

                        var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "),
                            function (v) {
                                return (/^(?!mso)/i.test(v));
                            }
                        );

                        return cls.length ? ' class="' + cls.join(" ") + '"' : '';
                    };

                    h = h.replace(/ class="([^"]+)"/gi, removeClasses);
                    h = h.replace(/ class=([\-\w]+)/gi, removeClasses);
                }

                // Remove spans option
                if (getParam(ed, "paste_remove_spans")) {
                    h = h.replace(/<\/?span[^>]*>/gi, "");
                }

                //console.log('After preprocess:' + h);

                o.content = h;
            }).apply(pl);
        },

        paste_postprocess: function (pl, o) {
            // convert br to p
            var paragraph = [];
            var result_container = $("<div></div>");
            var paragraph_count = 0;

            var style_tags = ["strong", "em", "b", "i"];
            var push_paragraph_hooks = [];
            push_paragraph_hooks.push(function (content, next_hook) {
                return content;
            });

            eval_paragraph = function (p) {
                if (typeof (p) == "function") {
                    return p();
                } else {
                    return p;
                }
            };

            push_paragraph = function (content) {
                paragraph.push(push_paragraph_hooks[push_paragraph_hooks.length - 1](content));
            };

            add_push_paragraph_hook = function (hook) {
                var next_hook = push_paragraph_hooks[push_paragraph_hooks.length - 1];
                push_paragraph_hooks.push(function (content) {
                    return hook(content, next_hook);
                });
                return push_paragraph_hooks.length;
            };

            remove_push_paragraph_hook = function (id) {
                if (push_paragraph_hooks.length != id) {
                    throw "Some hooks are not removed!";
                }
                push_paragraph_hooks.pop();
            };

            add_paragraph = function () {
                var newElem = $("<p></p>");
                $.each(paragraph, function (i, value) {
                    newElem.append(eval_paragraph(value));
                });
                paragraph = [];
                newElem.html($.trim((newElem.html() || '').replace(/^(\s|&nbsp;|\u3000)+|(\s|&nbsp;|\u3000)+$/g, '')) || "&nbsp;");
                result_container.append(newElem);
                paragraph_count++;
            };

            process_node = function () {
                var nodeName = this.nodeName.toLowerCase();
                if (nodeName == "br") {
                    add_paragraph();
                } else if (nodeName == "p" || nodeName == "div") {
                    if (paragraph.length > 0) {
                        add_paragraph();
                    }
                    var old_paragraph_count = paragraph_count;
                    $(this).contents().each(process_node);
                    if (paragraph.length > 0 || old_paragraph_count === paragraph_count) {
                        add_paragraph();
                    }
                } else if (nodeName == "span" || nodeName == "font") {
                    $(this).contents().each(process_node);
                } else if (style_tags.indexOf(nodeName) > -1) {
                    var elem = this;
                    (function (n) {
                        var id = add_push_paragraph_hook(function (content, next_hook) {
                            var newNode = $("<" + n + "/>");
                            newNode.append(content);
                            return next_hook(newNode);
                        });
                        $(elem).contents().each(process_node);
                        remove_push_paragraph_hook(id);
                    })(nodeName);
                } else if (nodeName == "#text") {
                    var trimmed = $.trim(this.textContent);
                    // For unknown reason, some \n is randomly inserted into text when pasting, remove them here
                    // !!! MUST USE RegExp !!!
                    trimmed = trimmed.replace(/\n/g, "");
                    if (trimmed != "") {
                        this.textContent = trimmed;
                        push_paragraph(this);
                    }
                } else {
                    push_paragraph(this);
                };
            };
            $(o.node).contents().each(process_node);
            if (paragraph.length > 0) {
                add_paragraph();
            }
            $(o.node).children().remove();
            $(o.node).append(result_container.children());
        }
    });

    $(container).parents("form").submit(function () {
        $(container).tinymce().save();
    });

    var uploader_running = false;
    var pending_upload_list = $(".pending-image-list", image_uploader);

    var px_per_em = 20;

    function upload_next_image(use_google_image_proxy) {
        if (uploader_running) {
            return;
        }
        if (pending_upload_list.children().size() == 0) {
            return
        }
        var current_elem = pending_upload_list.children().first();
        var url = current_elem.data("src");
        current_elem.text("Uploading " + url);
        uploader_running = true;

        function complete_upload(resp, error) {
            if (error) {
                current_elem.text(url + ": " + error).css("color", "red");
            } else {
                new_url = resp.fileName;
                current_elem.text(url + " -> " + new_url);
                var source_img = $($(current_elem).data("source_elem"));
                var aspect_ratio = (resp.width / resp.height)
                var desired_width_em = 20;
                var desired_height = desired_width_em / aspect_ratio * px_per_em;

                var target_height = Math.min(resp.height, desired_height);
                var target_height_em = target_height / px_per_em;
                source_img.removeAttr("width")
                            .removeAttr("height")
                            .removeAttr("data-mce-style")
                            .removeAttr("alt")
                            .removeAttr("hspace")
                            .removeAttr("vspace")
                            .removeAttr("align")
                            .attr("src", new_url)
                            .attr("data-mce-src", new_url)
                            .css({
                                "height": target_height_em + "em",
                                "cursor": ""
                            });
                if (target_height < resp.height) {
                    var img_link;
                    var target_width = target_height * aspect_ratio;
                    var thumb_url = "/Thumbnail/" + Math.round(target_width) + "x" + Math.round(target_height) + new_url;
                    source_img.attr("src", thumb_url)
                              .attr("data-mce-src", thumb_url);
                    if (source_img.parent().get(0).nodeName.toLowerCase() === "a") {
                        img_link = source_img.parent();
                    } else {
                        img_link = $("<a></a>");
                        source_img.replaceWith(img_link)
                                  .appendTo(img_link);
                    }
                    img_link.attr("href", new_url)
                                .attr("data-mce-href", new_url);
                }
                source_img.data("uploaded_image", "true"); // must set last, because data may be erased when moving elements
            }
            current_elem.remove().appendTo($(".uploaded-image-list", image_uploader));
            uploader_running = false;
            upload_next_image(use_google_image_proxy);
        }
        var cookies = $(".cookies", image_uploader).val();
        $.jStorage.set("image_uploader_last_used_cookies", cookies);
        if (use_google_image_proxy) {
            url = "https://images2-focus-opensocial.googleusercontent.com/gadgets/proxy?url=" +
                encodeURIComponent(url) +
                "&container=focus&gadget=a&no_expand=1&resize_h=0&rewriteMime=image%2F*";
        }
        $.ajax({
            type: 'POST',
            url: api_upload_image,
            dataType: "json",
            data: {
                url: url,
                cookies: cookies
            },
            success: function (resp) {
                if (resp.error) {
                    complete_upload(resp, resp.error);
                } else {
                    complete_upload(resp);
                }

            },
            error: function (xhr, textStatus) {
                complete_upload("", "Request error: " + textStatus);
            }
        });
    }
    $(".cookies", image_uploader).val($.jStorage.get("image_uploader_last_used_cookies", ''));
    $(".upload-images", image_uploader).click(function (e) {
        e.preventDefault();
        var contentContainer = $($(container).tinymce().contentDocument.body);
        // convert bbcode to <img/> first
        var contentHtml = contentContainer.html();
        var bbCodeImgRegex = /\[img\]([^\[]+)\[\/img]/gi;
        contentHtmlNew = contentHtml.replace(bbCodeImgRegex, "<img src='$1'/>");
        if (contentHtml !== contentHtmlNew) {
            contentContainer.html(contentHtmlNew);
        }
        var use_google_image_proxy = $(".use-google-image-proxy:checked").size() > 0;
        var images = $("img", contentContainer);
        images.each(function () {
            if ($(this).data("uploaded_image")) {
                return;
            }
            var src = $(this).attr("src");
            if ((/^https?:\/\/.*/).test(src)) {
                var listElem = $("<li></li>");
                listElem.text(src)
                            .data("source_elem", this)
                            .data("src", src);
                pending_upload_list.append(listElem);
            }
        });
        upload_next_image(use_google_image_proxy);
        return false;
    });
};
